<template>
  <div :style="{height: eleHeight}">
    <a-layout class="container">
      <a-layout-sider width="300" theme="light" class="select-area">
        <a-row>
          <a-checkable-tag
            v-model="tag.checked1"
            @change="toggleNodeShow1"
            class="tag"
            >基础节点</a-checkable-tag
          >
          <div align="center">
            <a-list :grid="{ gutter: 8, column: 2 }" v-if="tag.commonNodeShow">
              <a-list-item
                v-for="(commonNode, index) in field.commonNodes"
                :key="index"
              >
                <div
                  class="node-item"
                  :type="commonNode.type"
                  belongto="commonNodes"
                >
                  {{ commonNode.nodeName }}
                </div>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
        <a-row>
          <a-checkable-tag
            v-model="tag.checked2"
            @change="toggleNodeShow2"
            class="tag"
            >高级节点</a-checkable-tag
          >
          <div align="center">
            <a-list :grid="{ gutter: 8, column: 2 }" v-if="tag.highNodeShow">
              <a-list-item
                v-for="(highNode, index) in field.highNodes"
                :key="index"
              >
                <div
                  class="node-item"
                  :type="highNode.type"
                  belongto="highNodes"
                >
                  {{ highNode.nodeName }}
                </div>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
        <a-row>
          <a-checkable-tag
            v-model="tag.checked3"
            @change="toggleNodeShow3"
            class="tag"
            >泳道节点</a-checkable-tag
          >
          <div align="center">
            <a-list :grid="{ gutter: 8, column: 2 }" v-if="tag.laneNodeShow">
              <a-list-item
                v-for="(laneNode, index) in field.laneNodes"
                :key="index"
              >
                <div
                  class="node-item"
                  :type="laneNode.type"
                  belongto="laneNodes"
                >
                  {{ laneNode.nodeName }}
                </div>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
      </a-layout-sider>
      <a-layout>
        <a-layout-header class="header-option" style="padding: 0 25px">
          <!-- 顶部工具栏 -->
          <span v-for="(i, j) in field.tools" :key="j">
            <a-button
              class="header-option-button"
              @click="selectTool(i.type)"
              :icon="i.icon"
              size="small"
            >
              <span v-show="i.type!=='zoom-in' && i.type!=='zoom-out'">
                {{i.name}}
              </span>
            </a-button>
          </span>
        </a-layout-header>
        <a-layout-content class="content">
          <flow-area
            ref="flowArea"
            :browserType="browserType"
            :flowData="flowData"
            :select.sync="currentSelect"
            :selectGroup.sync="currentSelectGroup"
            :plumb="plumb"
            :currentTool="currentTool"
            @findNodeConfig="findNodeConfig"
            @selectTool="selectTool"
            @getShortcut="getShortcut"
            @saveFlow="saveFlow"
          >
          </flow-area>
          <vue-context-menu
            :contextMenuData="linkContextMenuData"
            @deleteLink="deleteLink"
          >
          </vue-context-menu>
        </a-layout-content>
      </a-layout>
      <a-layout-sider
        width="350"
        theme="light"
        class="attr-area"
        style="overflow-y: scroll;"
        @mousedown.stop="loseShortcut"
      >
        <flow-attr
          :plumb="plumb"
          :flowData="flowData"
          :select.sync="currentSelect"
          ref="selectForm"
        ></flow-attr>
      </a-layout-sider>
    </a-layout>
    <a-modal
      :title="'流程设计图_' + flowData.attr.id + '.png'"
      centered
      width="90%"
      :closable="flowPicture.closable"
      :maskClosable="flowPicture.maskClosable"
      :visible="flowPicture.modalVisible"
      okText="下载到本地"
      cancelText="取消"
      @ok="downLoadFlowPicture"
      @cancel="cancelDownLoadFlowPicture"
    >
      <div align="center" style="width: 100%; height: 100%; overflow: auto">
        <img :src="flowPicture.url" style="width: 100%" />
      </div>
    </a-modal>
    <setting-modal ref="settingModal"></setting-modal>
    <shortcut-modal ref="shortcutModal"></shortcut-modal>
    <test-modal ref="testModal" @loadFlow="loadFlow"></test-modal>
  </div>
</template>

<script>
import jsplumb from 'jsplumb'
import {
  tools,
  commonNodes,
  highNodes,
  laneNodes
} from './config/basic-node-config.js'
import { flowConfig } from './config/args-config.js'
import $ from 'jquery'
import 'jquery-ui/ui/widgets/draggable'
import 'jquery-ui/ui/widgets/droppable'
import 'jquery-ui/ui/widgets/resizable'
import html2canvas from 'html2canvas'
import canvg from 'canvg'
import { eventMethod } from './util/eventMethod.js'
import FlowArea from './modules/FlowArea'
import FlowAttr from './modules/FlowAttr'
import SettingModal from './modules/SettingModal'
import ShortcutModal from './modules/ShortcutModal'
import UsingDocModal from './modules/UsingDocModal'
import TestModal from './modules/TestModal'

export default {
  name: 'vfd',
  components: {
    jsplumb,
    flowConfig,
    html2canvas,
    canvg,
    FlowArea,
    FlowAttr,
    SettingModal,
    ShortcutModal,
    UsingDocModal,
    TestModal
  },
  mounted () {
    let tkHeight = document.getElementsByClassName('ant-modal-wrap')[0].offsetHeight
    let titHeight = document.getElementsByClassName('ant-modal-header')[0].offsetHeight
    this.eleHeight = (tkHeight - titHeight) + 'px'
    this.dealCompatibility()
    this.initNodeSelectArea()
    this.initJsPlumb()
    this.listenShortcut()
    this.initFlow()
    // this.listenPage()
  },
  data () {
    return {
      eleHeight: '500px',
      tag: {
        checked0: true,
        checked1: true,
        checked2: true,
        checked3: true,
        toolShow: true,
        commonNodeShow: true,
        highNodeShow: true,
        laneNodeShow: true
      },
      browserType: 3,
      plumb: {},
      field: {
        tools: tools,
        commonNodes: commonNodes,
        highNodes: highNodes,
        laneNodes: laneNodes
      },
      flowData: {
        nodeList: [],
        linkList: [],
        attr: {
          id: '',
          persistId: '',
          name: '',
          customProcess: {}
        },
        status: flowConfig.flowStatus.CREATE,
        remarks: [],
        previewImage: ''
      },
      currentTool: {
        type: 'drag',
        icon: 'drag',
        name: '拖拽'
      },
      currentSelect: {},
      currentSelectGroup: [],
      activeShortcut: true,
      linkContextMenuData: flowConfig.contextMenu.link,
      flowPicture: {
        url: '',
        modalVisible: false,
        closable: false,
        maskClosable: false
      }
    }
  },
  methods: {
    toggleNodeShow0 (flag) {
      if (!flag) {
        this.tag.toolShow = false
      } else {
        this.tag.toolShow = true
      }
    },
    toggleNodeShow1 (flag) {
      if (!flag) {
        this.tag.commonNodeShow = false
      } else {
        this.tag.commonNodeShow = true
      }
    },
    toggleNodeShow2 (flag) {
      if (!flag) {
        this.tag.highNodeShow = false
      } else {
        this.tag.highNodeShow = true
      }
    },
    toggleNodeShow3 (flag) {
      if (!flag) {
        this.tag.laneNodeShow = false
      } else {
        this.tag.laneNodeShow = true
      }
    },
    getBrowserType () {
      let userAgent = navigator.userAgent
      let isOpera = userAgent.indexOf('Opera') > -1
      if (isOpera) {
        return 1
      }
      if (userAgent.indexOf('Firefox') > -1) {
        return 2
      }
      if (userAgent.indexOf('Chrome') > -1) {
        return 3
      }
      if (userAgent.indexOf('Safari') > -1) {
        return 4
      }
      if (
        userAgent.indexOf('compatible') > -1 &&
        userAgent.indexOf('MSIE') > -1 &&
        !isOpera
      ) {
        alert('IE浏览器支持性较差，推荐使用Firefox或Chrome')
        return 5
      }
      if (userAgent.indexOf('Trident') > -1) {
        alert('Edge浏览器支持性较差，推荐使用Firefox或Chrome')
        return 6
      }
    },
    dealCompatibility () {
      this.browserType = this.getBrowserType()
      if (this.browserType === 2) {
        flowConfig.shortcut.scaleContainer = {
          code: 16,
          codeName: 'SHIFT(chrome下为ALT)',
          shortcutName: '画布缩放'
        }
      }
    },
    initJsPlumb () {
      this.plumb = jsplumb.jsPlumb.getInstance(flowConfig.jsPlumbInsConfig)
      this.plumb.bind('beforeDrop', (info) => {
        // 避免连线源锚点和目标锚点在同一节点上
        if (info.sourceId === info.targetId) return false
        return true
      })
      this.plumb.bind('connection', (conn, e) => {
        let connObj = conn.connection.canvas
        let o = {}
        let id
        let label
        if (
          this.flowData.status === flowConfig.flowStatus.CREATE ||
          this.flowData.status === flowConfig.flowStatus.MODIFY
        ) {
          id = 'link-' + eventMethod.getId()
          label = ''
        } else if (this.flowData.status === flowConfig.flowStatus.LOADING) {
          let l = this.flowData.linkList[this.flowData.linkList.length - 1]
          id = l.id
          label = l.label
        }
        connObj.id = id
        o.type = 'link'
        o.id = id
        o.sourceId = conn.sourceId
        o.targetId = conn.targetId
        o.label = label
        o.cls = {
          linkType: flowConfig.jsPlumbInsConfig.Connector[0],
          linkColor: flowConfig.jsPlumbInsConfig.PaintStyle.stroke,
          linkThickness: flowConfig.jsPlumbInsConfig.PaintStyle.strokeWidth
        }
        $('#' + id).bind('contextmenu', (e) => {
          this.showLinkContextMenu(e)
          this.currentSelect = this.flowData.linkList.filter(
            (l) => l.id === id
          )[0]
        })
        $('#' + id).bind('click', (e) => {
          let event = window.event || e
          event.stopPropagation()
          this.currentSelect = this.flowData.linkList.filter(
            (l) => l.id === id
          )[0]
        })
        if (this.flowData.status !== flowConfig.flowStatus.LOADING) { this.flowData.linkList.push(o) }
      })

      this.plumb.importDefaults({
        ConnectionsDetachable: flowConfig.jsPlumbConfig.conn.isDetachable
      })
    },
    initNodeSelectArea () {
      $(document).ready(() => {
        $('.node-item').draggable({
          opacity: flowConfig.defaultStyle.dragOpacity,
          helper: 'clone',
          cursorAt: {
            top: 16,
            left: 60
          },
          containment: 'window',
          revert: 'invalid'
        })
      })
    },
    listenShortcut () {
      document.onkeydown = (e) => {
        let event = window.event || e
        if (!this.activeShortcut) return
        let key = event.keyCode
        switch (key) {
          case flowConfig.shortcut.multiple.code:
            this.$refs.flowArea.rectangleMultiple.flag = true
            break
          case flowConfig.shortcut.dragContainer.code:
            this.$refs.flowArea.container.dragFlag = true
            break
          case flowConfig.shortcut.scaleContainer.code:
            this.$refs.flowArea.container.scaleFlag = true
            break
          case flowConfig.shortcut.dragTool.code:
            this.selectTool('drag')
            break
          case flowConfig.shortcut.connTool.code:
            this.selectTool('connection')
            break
          case flowConfig.shortcut.zoomInTool.code:
            this.selectTool('zoom-in')
            break
          case flowConfig.shortcut.zoomOutTool.code:
            this.selectTool('zoom-out')
            break
          case flowConfig.shortcut.delNode.code:
            if (this.$refs.selectForm.activeKey === 'link-attr') {
              this.deleteLink()
            } else if (this.$refs.selectForm.activeKey === 'node-attr') {
              this.$refs.flowArea.deleteNode()
            }
            break
          case 37:
            this.moveNode('left')
            break
          case 38:
            this.moveNode('up')
            break
          case 39:
            this.moveNode('right')
            break
          case 40:
            this.moveNode('down')
            break
        }
        if (event.ctrlKey) {
          if (event.altKey) {
            switch (key) {
              case flowConfig.shortcut.settingModal.code:
                this.setting()
                break
              case flowConfig.shortcut.testModal.code:
                this.openTest()
                break
            }
          }
        }
      }
      document.onkeyup = (e) => {
        let event = window.event || e
        let key = event.keyCode
        if (key === flowConfig.shortcut.dragContainer.code) {
          this.$refs.flowArea.container.dragFlag = false
        } else if (key === flowConfig.shortcut.scaleContainer.code) {
          event.preventDefault()
          this.$refs.flowArea.container.scaleFlag = false
        } else if (key === flowConfig.shortcut.multiple.code) {
          this.$refs.flowArea.rectangleMultiple.flag = false
        }
      }
    },
    listenPage () {
      window.onbeforeunload = (e) => {
        e = e || window.event
        if (e) {
          e.returnValue = '关闭提示'
        }
        return '关闭提示'
      }
    },
    initFlow () {
      if (this.flowData.status === flowConfig.flowStatus.CREATE) {
        this.flowData.attr.id = 'flow-' + eventMethod.getId()
      } else {
        this.loadFlow()
      }
    },
    loadFlow (json) {
      this.clear()
      let loadData = JSON.parse(json)
      this.flowData.attr = loadData.attr
      this.flowData.config = loadData.config
      this.flowData.status = flowConfig.flowStatus.LOADING
      this.plumb.batch(() => {
        let nodeList = loadData.nodeList
        nodeList.forEach((node, index) => {
          this.flowData.nodeList.push(node)
        })
        let linkList = loadData.linkList
        this.$nextTick(() => {
          linkList.forEach((link, index) => {
            this.flowData.linkList.push(link)
            let conn = this.plumb.connect({
              source: link.sourceId,
              target: link.targetId,
              anchor: flowConfig.jsPlumbConfig.anchor.default,
              connector: [
                link.cls.linkType,
                {
                  gap: 5,
                  cornerRadius: 8,
                  alwaysRespectStubs: true
                }
              ],
              paintStyle: {
                stroke: link.cls.linkColor,
                strokeWidth: link.cls.linkThickness
              }
            })
            if (link.label !== '') {
              conn.setLabel({
                label: link.label,
                cssClass: 'linkLabel'
              })
            }
          })
          this.currentSelect = {}
          this.currentSelectGroup = []
          this.flowData.status = flowConfig.flowStatus.MODIFY
        })
      }, true)
      let canvasSize = this.computeCanvasSize()
      this.$refs.flowArea.container.pos = {
        top: -canvasSize.minY + 100,
        left: -canvasSize.minX + 100
      }
    },
    findNodeConfig (belongto, type, callback) {
      let node = null
      switch (belongto) {
        case 'commonNodes':
          node = commonNodes.filter((n) => n.type === type)
          break
        case 'highNodes':
          node = highNodes.filter((n) => n.type === type)
          break
        case 'laneNodes':
          node = laneNodes.filter((n) => n.type === type)
          break
      }
      if (node && node.length >= 0) node = node[0]
      callback(node)
    },
    selectTool (type) {
      let tool = tools.filter((t) => t.type === type)
      if (tool && tool.length >= 0) this.currentTool = tool[0]
      switch (type) {
        case 'drag':
          this.changeToDrag()
          break
        case 'connection':
          this.changeToConnection()
          break
        case 'zoom-in':
          this.changeToZoomIn()
          break
        case 'zoom-out':
          this.changeToZoomOut()
          break
        case 'save':
          this.exportFlowPicture() // 原来的生成图片
          break
        case 'picture':
          this.saveFlow() // 原来的保存
          break
        case 'delete':
          this.clear()
          break
        case 'setting':
          this.setting()
          break
        case 'book':
          this.shortcutHelper()
          break
        case 'logout':
          this.exit()
          break
      }
    },
    changeToDrag () {
      this.flowData.nodeList.forEach((node) => {
        let f = this.plumb.toggleDraggable(node.id)
        if (!f) {
          this.plumb.toggleDraggable(node.id)
        }
        if (node.type !== 'x-lane' && node.type !== 'y-lane') {
          this.plumb.unmakeSource(node.id)
          this.plumb.unmakeTarget(node.id)
        }
      })
    },
    changeToConnection () {
      this.flowData.nodeList.forEach((node) => {
        let f = this.plumb.toggleDraggable(node.id)
        if (f) {
          this.plumb.toggleDraggable(node.id)
        }
        if (node.type !== 'x-lane' && node.type !== 'y-lane') {
          this.plumb.unmakeSource(node.id)
          this.plumb.unmakeTarget(node.id)
          this.plumb.makeSource(
            node.id,
            flowConfig.jsPlumbConfig.makeSourceConfig
          )
          this.plumb.makeTarget(
            node.id,
            flowConfig.jsPlumbConfig.makeTargetConfig
          )
        }
      })
      this.currentSelect = {}
      this.currentSelectGroup = []
    },
    changeToZoomIn () {
      this.$message.info('切换到放大工具')
    },
    changeToZoomOut () {
      this.$message.info('切换到缩小工具')
    },
    checkFlow () {
      let nodeList = this.flowData.nodeList
      if (nodeList.length <= 0) {
        this.$message.error('流程图中无任何节点！')
        return false
      }
      return true
    },
    saveFlow () {
      let flowObj = Object.assign({}, this.flowData)
      if (!this.checkFlow()) return false
      if (flowObj.previewImage !== '') {
        flowObj.status = flowConfig.flowStatus.SAVE
        let d = JSON.stringify(flowObj)
        console.log(d)
        this.$emit('saveFlow', d)
        return true
      }
    },
    exportFlowPicture () {
      if (!this.checkFlow()) return
      // 获取节点VFD流程编辑器里面画的节点
      let $Container = this.$refs.flowArea.$el.children[0]
      // 获取线条放进数组
      let svgElems = $($Container).find('svg[id^="link-"]')
      let removeArr = []
      svgElems.each((index, svgElem) => {
        // 创建canvas
        let linkCanvas = document.createElement('canvas')
        // 设置canvas的id,可自行设置
        let canvasId = 'linkCanvas-' + eventMethod.getId()
        linkCanvas.id = canvasId
        removeArr.push(canvasId)
        // 获取线条转成字符串
        let svgContent = svgElem.outerHTML.trim()
        // 利用canvg处理svg转png 注意这里canvg的插件是2.0.0版本。其他版本可能不是这样的用法
        canvg(linkCanvas, svgContent)
        // 设置线条的位置，保证跟画的流程节点位置一致
        if (svgElem.style.position) {
          linkCanvas.style.position += svgElem.style.position
          linkCanvas.style.left += svgElem.style.left
          linkCanvas.style.top += svgElem.style.top
        }
        // 把生成的canvas线条放进$Container里
        $($Container).append(linkCanvas)
      })
      // 这个方法是生成图片（节点）的位置大小
      let canvasSize = this.computeCanvasSize()
      let pbd = flowConfig.defaultStyle.photoBlankDistance
      let offsetPbd = eventMethod.div(pbd, 2)
      html2canvas($Container, {
        width: canvasSize.width + pbd,
        height: canvasSize.height + pbd,
        scrollX: -canvasSize.minX + offsetPbd,
        scrollY: -canvasSize.minY + offsetPbd,
        logging: false,
        onclone: () => {
          // 删除前面添加的canvas节点
          removeArr.forEach((id) => {
            $('#' + id).remove()
          })
        }
      }).then((canvas) => {
        let dataURL = canvas.toDataURL('image/png')
        this.flowData.previewImage = dataURL
        return this.saveFlow()
        // this.flowPicture.url = dataURL
        // this.flowPicture.modalVisible = true
      })
    },
    downLoadFlowPicture () {
      let alink = document.createElement('a')
      let alinkId = 'alink-' + eventMethod.getId()
      alink.id = alinkId
      alink.href = this.flowPicture.url
      alink.download = '流程设计图_' + this.flowData.attr.id + '.png'
      alink.click()
    },
    cancelDownLoadFlowPicture () {
      this.flowPicture.url = ''
      this.flowPicture.modalVisible = false
    },
    computeCanvasSize () {
      let nodeList = Object.assign([], this.flowData.nodeList)
      let minX = nodeList[0].x
      let minY = nodeList[0].y
      let maxX = nodeList[0].x + nodeList[0].width
      let maxY = nodeList[0].y + nodeList[0].height
      nodeList.forEach((node) => {
        minX = Math.min(minX, node.x)
        minY = Math.min(minY, node.y)
        maxX = Math.max(maxX, node.x + node.width)
        maxY = Math.max(maxY, node.y + node.height)
      })
      let canvasWidth = maxX - minX
      let canvasHeight = maxY - minY
      return {
        width: canvasWidth,
        height: canvasHeight,
        minX: minX,
        minY: minY,
        maxX: maxX,
        maxY: maxY
      }
    },
    clear () {
      this.flowData.nodeList.forEach((node) => {
        this.plumb.remove(node.id)
      })
      this.currentSelect = {}
      this.currentSelectGroup = []
      this.flowData.nodeList = []
      this.flowData.linkList = []
      this.flowData.remarks = []
    },
    setting () {
      this.$refs.settingModal.open()
    },
    shortcutHelper () {
      this.$refs.shortcutModal.open()
    },
    exit () {
      this.$emit('close')
    },
    showLinkContextMenu (e) {
      let event = window.event || e
      event.preventDefault()
      event.stopPropagation()
      $('.vue-contextmenuName-flow-menu').css('display', 'none')
      $('.vue-contextmenuName-node-menu').css('display', 'none')
      let x = event.clientX
      let y = event.clientY
      this.linkContextMenuData.axis = { x, y }
    },
    deleteLink () {
      let sourceId = this.currentSelect.sourceId
      let targetId = this.currentSelect.targetId
      this.plumb.deleteConnection(
        this.plumb.getConnections({
          source: sourceId,
          target: targetId
        })[0]
      )
      let linkList = this.flowData.linkList
      linkList.splice(
        linkList.findIndex(
          (link) => link.sourceId === sourceId || link.targetId === targetId
        ),
        1
      )
      this.currentSelect = {}
    },
    loseShortcut () {
      this.activeShortcut = false
    },
    getShortcut () {
      this.activeShortcut = true
    },
    openTest () {
      let flowObj = Object.assign({}, this.flowData)
      this.$refs.testModal.flowData = flowObj
      this.$refs.testModal.testVisible = true
    },
    moveNode (type) {
      let m = flowConfig.defaultStyle.movePx
      let isX = true
      switch (type) {
        case 'left':
          m = -m
          break
        case 'up':
          m = -m
          isX = false
          break
        case 'right':
          break
        case 'down':
          isX = false
      }
      if (this.currentSelectGroup.length > 0) {
        this.currentSelectGroup.forEach((node) => {
          if (isX) {
            node.x += m
          } else {
            node.y += m
          }
        })
        this.plumb.repaintEverything()
      } else if (this.currentSelect.id) {
        if (isX) {
          this.currentSelect.x += m
        } else {
          this.currentSelect.y += m
        }
        this.plumb.repaintEverything()
      }
    },
    getDataString () {
      let flowObj = JSON.parse(JSON.stringify(this.flowData))
      delete flowObj.status
      return JSON.stringify(flowObj)
    }
  }
}
</script>

<style lang="less" scoped>
@import "./style/flow-designer.less";
</style>
